import { CACHE_MANAGER, ConflictException, Inject, Injectable, NotFoundException } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { In, Repository } from 'typeorm'
import { CacheUserUpdateDto, UpdateUserDto, User, UserLinkTypes, UserWebhook } from '@boticord/users-client'
import { Cache } from 'cache-manager'
import { BotClientService } from '@boticord/bot-client'
import { ErrorCodes, TimeLimits } from '@boticord/consts'
import { AuthService } from '@boticord/auth'
import { emptyToNull } from '@boticord/common'

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User) private readonly users: Repository<User>,
    @InjectRepository(UserWebhook) private readonly webhooks: Repository<UserWebhook>,
    @Inject(CACHE_MANAGER) private readonly cache: Cache,
    public readonly bot: BotClientService,
    public readonly auth: AuthService,
  ) {}

  // this key is used to access cached discord user data
  private discordUserCacheKey = (userID: string) => `discord:${userID}:lastUpdated`

  async updateUserDBCache(user: CacheUserUpdateDto) {
    let usr = await this.users.findOne({ where: { id: user.id } })

    // create new user if it doesn't exist
    if (usr === null) {
      usr = new User()
      usr.badges = []
      usr.id = user.id
    }

    usr.discriminator = user.discriminator
    usr.username = user.username
    usr.avatar = user.avatar

    await this.users.save(usr)
  }


  async populateCacheUsingDiscord(userID: string) {
    // save to redis cache to know it has been recently updated
    const did = await this.cache.get<boolean>(this.discordUserCacheKey(userID))
    if (did) return

    const discordUser = await this.bot.getUser(userID)
    if (!discordUser) throw new NotFoundException({ code: ErrorCodes.UnknownUser })

    await this.updateUserDBCache({
      id: userID,
      discriminator: discordUser.discriminator,
      username: discordUser.username,
      avatar: discordUser.avatar,
    })

    // actually update the cache
    await this.cache.set(this.discordUserCacheKey(userID), true, { ttl: TimeLimits.DiscordUserCache / 1000 })

    return discordUser
  }

  // WTF: User or CreateUserDto?
  async create(user: User /* <------- */) {
    return this.users.save(user)
  }

  async get(userID: string) {
    let user = await this.users.findOne({ where: { id: userID }, relations: { badges: true } })

    if (!user) {
      user = await this.users.findOne({ where: { shortDomain: userID }, relations: { badges: true } })

      if (!user) throw new NotFoundException({ code: ErrorCodes.UnknownUser })
    }

    return { ...user, ...await this.populateCacheUsingDiscord(user.id) }
  }

  getWebhook(id: string) {
    return this.webhooks.findOne({ where: { id } })
  }

  /** get user by short link */
  async getByShort(shortDomain: string) {
    const user = await this.users.findOne({ where: { shortDomain } })
    if (user === null) {
      return null
    }
    
    return this.get(user.id)
  }

  async getMany(ids: string[]) {
    const users = await this.users.find({ where: { id: In(ids) } })

    const result = {}

    for (const user of users) {
      result[user.id] = {
        id: user.id,
        username: user.username,
        discriminator: user.discriminator,
        avatar: user.avatar,
      }
    }

    return result
  }

  // WTF: User or UpdateUserDto?
  async update(id: string, update: UpdateUserDto) {
    const user = await this.users.findOne({ where: { id } })
    if (!user) throw new NotFoundException({ code: ErrorCodes.UnknownUser })

    if (update.shortDomain) {
      const checkTaken = await this.getByShort(update.shortDomain)
      if (checkTaken !== null && checkTaken.id !== user.id) {
        throw new ConflictException({ code: ErrorCodes.ShortLinkTaken })
      }
      user.shortDomain = update.shortDomain
    }

    user.description = emptyToNull(update.description)
    user.socials = {
      [UserLinkTypes.Vk]: emptyToNull(update.socials?.[UserLinkTypes.Vk]),
      [UserLinkTypes.Git]: emptyToNull(update.socials?.[UserLinkTypes.Git]),
      [UserLinkTypes.Telegram]: emptyToNull(update.socials?.[UserLinkTypes.Telegram]),
      [UserLinkTypes.Donate]: emptyToNull(update.socials?.[UserLinkTypes.Donate]),
      [UserLinkTypes.Custom]: emptyToNull(update.socials?.[UserLinkTypes.Custom]),
    }
    user.shortDescription = emptyToNull(update.shortDescription)

    if (update.webhook) {
      const webhook = new UserWebhook()
      webhook.url = update.webhook.url
      webhook.headers = update.webhook.headers ?? {}
      webhook.enabled = update.webhook.enabled ?? false
      webhook.id = user.id
      user.webhook = webhook

      return await this.users.save(user)
    }

    return this.users.save(user)
  }
}
